/**
 * @deprecated monaco element
 */

import { attribute } from '../../utils/observer'
import { eachArray, removeInvalidTextNode, styleCssRule } from '../../utils/utils'
import { getCss, proxyAttribute, setCss } from '../core/base'
import { createShadow, createTemplate } from './func/tpl'

export default class MoancoElement extends HTMLElement {
    get value() {
        if (this.iframe.contentWindow.instance) {
            return this.iframe.contentWindow.instance.getValue()
        }
        return ''
    }

    set value(val) {
        // 转义处理
        if (!this.encode.reg.test(val)) {
            val = `${this.encode.text}${encodeURI(val)}`
        }
        this.attr.value = val
        return val
    }

    get language() {
        return this.shadowRoot.querySelector('#language').value
    }

    set language(val) {
        this.attr.language = val
        return val
    }

    get theme() {
        return this.shadowRoot.querySelector('#theme').value
    }

    set theme(val) {
        this.attr.theme = val
        return this.theme
    }

    constructor() {
        super()

        // 密文前缀
        this.encode = {
            reg: /^encodeURI/,
            text: 'encodeURI',
        }

        this.attr = proxyAttribute(this)

        // :host 的 css 规则
        this.hostCss = new Map([
            ['width', '100%'],
            ['height', '500px'],
            ['display', 'inline-block'],
        ])

        // attribute 与 css 的映射
        this.map = new Map([
            ['width', 'width'],
            ['height', 'height'],
        ])

        const shadow = this.attachShadow({ mode: 'open' })
        shadow.innerHTML = createShadow()

        // 子跟节点（shadow 下的根节点）
        this.root = shadow.querySelector('.container')

        // 编辑器节点（iframe）
        this.iframe = shadow.querySelector('iframe')

        removeInvalidTextNode(this.root)

        // 编程语言切换（下拉选择框）
        const lang = shadow.querySelector('#language')
        lang.addEventListener('change', () => {
            this.attr.language = lang.value
        })

        // 编辑器主题切换（下拉选择框）
        const theme = shadow.querySelector('#theme')
        theme.addEventListener('change', () => {
            this.attr.theme = theme.value
        })
    }

    /**
     * 初始化 monaco 编辑器
     */
    render() {
        const iframe = this.iframe
        if (iframe.contentWindow.instance) {
            return Promise.resolve({ instance: iframe.contentWindow.instance, monaco: iframe.contentWindow.monaco })
        }
        return new Promise((resolve, reject) => {
            // 加载超时的时间
            const end = Date.now() + 1000 * 60 * 5

            if (!iframe.hasAttribute('loading')) {
                // 添加 “加载中” 的标记（避免重复写入）
                iframe.setAttribute('loading', '')
                    // 将 HTML 模板写入 iframe
                iframe.srcdoc = createTemplate({ language: this.attr.language || 'plaintext', theme: 'vs' })
            }

            // 监听 Monaco Editor 是否成功初始化
            const listener = () => {
                if (iframe.contentWindow.instance) {
                    // 删除 “加载中” 的标记
                    iframe.removeAttribute('loading')
                        // Monaco Editor 的实例
                    const instance = iframe.contentWindow.instance
                        // 监听 Monaco Editor 的失焦事件
                    instance.onDidBlurEditorText(() => {
                            this.value = instance.getValue()
                        })
                        // 返回 Monaco 的全局变量和 Monaco Editor 的实例
                    resolve({ instance: instance, monaco: iframe.contentWindow.monaco })
                } else if (end < Date.now()) {
                    // 删除 “加载中” 的标记
                    iframe.removeAttribute('loading')
                    reject(new Error('monaco editor loading failed'))
                } else {
                    requestAnimationFrame(listener)
                }
            }
            listener()
        })
    }

    /**
     * 将设置在 attribute 上的值同步到 monaco 编辑器上
     */
    updateMonacoValue() {
        let value = this.attr.value
        if (value !== null && value !== this.ciphertext) {
            const reg = this.encode.reg
            if (reg.test(value)) {
                // 缓存本次设置的值（密文字符串）
                this.ciphertext = value
                    // 将值同步到 Monaco 编辑器
                this.render().then(({ instance }) => {
                    instance.setValue(decodeURI(value.replace(reg, '')))
                })
            } else {
                this.value = value // 通过二次设置将 value attribute 进行转义处理了
            }
        }
    }

    /**
     * 更新编辑器主题
     */
    updateMonacoTheme() {
        let value = this.attr.theme
        if (!['vs', 'vs-dark', 'hc-black'].includes(value)) {
            value = 'vs'
        }
        switch (value) {
            case 'vs-dark':
                this.root.classList.remove('hc-black')
                this.root.classList.add('vs-dark')
                break
            case 'hc-black':
                this.root.classList.remove('vs-dark')
                this.root.classList.add('hc-black')
                break
            default:
                this.root.classList.remove('vs-dark', 'hc-black')
                break
        }
        this.render().then(({ monaco }) => {
            this.shadowRoot.querySelector('#theme').value = value
            monaco.editor.setTheme(value)
        })
    }

    /**
     * 加载 languages 数据（被支持的语言集合）
     */
    loadLanguages() {
        if (Array.isArray(this.languages)) {
            return Promise.resolve(this.languages)
        }
        return this.render().then(({ monaco }) => {
            // 获取 monaco 编辑器支持的所有语言
            this.languages = monaco.languages
                .getLanguages()
                .map(row => row.id)
                .sort()

            // 获取当前的设置语言
            const selected = this.attr.language

            // 更新语言选择的下拉框
            const select = this.shadowRoot.querySelector('#language')
            select.innerHTML = this.languages.map(lang => `<option value="${lang}"${lang === selected ? ' selected' : ''}>${lang}</option>`).join('')

            // 返回所有的语言集合
            return Promise.resolve(this.languages)
        })
    }

    /**
     * 更新 monaco 编辑器当前的语言支持
     * @param {String} selected 语言 id
     * @returns
     */
    updateMonacoLanguage(selected) {
        return this.loadLanguages().then(languages => {
            // 设置的语言被支持
            if (languages.includes(selected)) {
                return this.render().then(({ monaco, instance }) => {
                    const value = instance.getValue()

                    // 销毁旧的语言模块实例
                    const oldm = instance.getModel()
                    oldm && oldm.dispose()

                    // 设置新的语言模块支持
                    const newm = monaco.editor.createModel(value, selected)
                    instance.setModel(newm)

                    // 同步到下拉选择框
                    this.shadowRoot.querySelector('#language').value = selected
                    return Promise.resolve()
                })
            } else {
                return Promise.reject(new Error(`不被支持的语言：${selected}`))
            }
        })
    }

    /**
     * 初次连接到 DOM 树的生命周期
     */
    connectedCallback() {
        // 异步初始化 Monaco Editor 并赋值
        this.render().then(() => {
            this.updateMonacoTheme()
            this.updateMonacoLanguage(this.attr.language)
                .then(() => {
                    this.updateMonacoValue()
                })
                .catch(() => {
                    this.updateMonacoValue()
                })
        })

        const hostRule = styleCssRule(this.shadowRoot.querySelector('style'), ':host')

        // 初始化 css 样式
        this.hostCss.forEach((v, k) => {
            hostRule.style.setProperty(k, v)
        })

        // 将 attribute 同步到子跟节点
        Reflect.ownKeys(this.attr).forEach(k => {
            if (this.map.has(k)) {
                hostRule.style.setProperty(k, this.attr[k])
            }
        })

        // attribute 监听回调
        const fn = records => {
            eachArray(records, ({ attributeName }) => {
                switch (attributeName) {
                    case 'value':
                        this.updateMonacoValue()
                        break
                    case 'theme':
                        this.updateMonacoTheme()
                        break
                    case 'language':
                        this.updateMonacoLanguage(this.attr.language)
                        break
                    default:
                        if (this.map.has(attributeName)) {
                            const name = this.map.get(attributeName)
                            if (this.hasAttribute(attributeName)) {
                                hostRule.style.setProperty(name, this.getAttribute(attributeName))
                            } else if (this.hostCss.has(name)) {
                                hostRule.style.setProperty(name, this.hostCss.get(name))
                            } else {
                                hostRule.style.removeProperty(name)
                            }
                        }
                        break
                }
            })
        }

        attribute(this, fn, false, ['language', 'theme', 'value', 'width', 'height'])
    }

    getCss(key) {
        return getCss(this, key)
    }

    setCss(key, val) {
        setCss(this, key, val)
        return this
    }
}